package org.pblue.appengine.data.bean;

import java.util.HashSet;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.Inheritance;
import javax.jdo.annotations.InheritanceStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import org.pblue.appengine.data.AppengineDataException;
import org.pblue.appengine.data.AppengineDataManager;
import org.pblue.appengine.data.caching.Cache;
import org.pblue.appengine.data.caching.CacheBean;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

@SuppressWarnings("serial")
@PersistenceCapable(identityType=IdentityType.APPLICATION, detachable="true")
@Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE)
public class DataBean extends CacheBean {
  
  @PrimaryKey
  @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
  private Key key;
  
  @Persistent
  private String id;
  
  protected DataBean(String pId) {
    this(pId, null);
  }
  
  protected DataBean(String pId, DataBean pParent) {
    this.setId(pId);
    
    Key key = DataBean.generateKey(this.getClass(), pId, pParent);
    this.setKey(key);       
    
    init();
  }
  
  protected void init() {
    this.setCacheKey(this.getKey());
    this.setDependencies(new HashSet<Object>());
  }
  
  protected static Key generateKey(Class<? extends DataBean> pDataClass, String pId, DataBean pParent) {
    if (pParent == null) {
      return KeyFactory.createKey(pDataClass.getSimpleName(), pId);
    } else {
      KeyFactory.Builder keyBuilder = new KeyFactory.Builder(pParent.getClass().getSimpleName(), pParent.getId());
      keyBuilder.addChild(pDataClass.getSimpleName(), pId);
      return keyBuilder.getKey();
    }
  }
  
  public static DataBean get(Class<? extends DataBean> pDataClass, String pId, DataBean pParent) throws AppengineDataException {
    AppengineDataManager dataManager = AppengineDataManager.open();
    DataBean dataBean;
    try {
      dataBean = DataBean.get(pDataClass, pId, pParent, dataManager);
    } finally {
      dataManager.close();
    }
    return dataBean;
  }
  
  public static DataBean get(Class<? extends DataBean> pDataClass, String pId, DataBean pParent, AppengineDataManager pDataManager) throws AppengineDataException {
    DataBean dataBean = (DataBean) Cache.get(pId);
    
    if (dataBean == null) {
      try {
        Key key = DataBean.generateKey(pDataClass, pId, pParent);
        dataBean = (DataBean) pDataManager.getCurrentPersistenceManager().getObjectById(pDataClass, key);
      } catch (JDOObjectNotFoundException e) {
      } catch (Exception e) {
        throw new AppengineDataException("Unable to retrieve " + pDataClass.getSimpleName() + " from datastore with id " + pId, e);
      }
      
      if (dataBean != null) {
        dataBean.init();
        Cache.add(dataBean);
        pDataManager.addCacheKeyToTransactions(dataBean.getCacheKey());
      }
    }
    
    return dataBean;
  }
  
  public void remove() throws AppengineDataException {
    AppengineDataManager dataManager = AppengineDataManager.open();
    try {
      remove(dataManager);
    } finally {
      dataManager.close();
    }
  }
  
  public void remove(AppengineDataManager pDataManager) throws AppengineDataException {
    Cache.remove(this);
    
    try {
      pDataManager.getCurrentPersistenceManager().deletePersistent(this);
    } catch (Exception e) {
      throw new AppengineDataException("Unable to remove " + this.getClass().getSimpleName() + " from datastore with id " + getId(), e);
    }
  }
  
  public void save() throws AppengineDataException {
    AppengineDataManager dataManager = AppengineDataManager.open();
    try {
      save(dataManager);
    } finally {
      dataManager.close();
    }
  }
  
  public void save(AppengineDataManager pDataManager) throws AppengineDataException {
    Cache.remove(this);
    
    try {
      pDataManager.getCurrentPersistenceManager().makePersistent(this);
      Cache.add(this);
      pDataManager.addCacheKeyToTransactions(getCacheKey());
    } catch (Exception e) {
      throw new AppengineDataException("Unable to save " + this.getClass().getSimpleName() + " to datastore with id " + getId(), e);
    }
  }
  
  public Key getKey() {
    return key;
  }
  
  public void setKey(Key pKey) {
    key = pKey;
  }
  
  public String getId() {
    return id;
  }
  
  public void setId(String pId) {
    id = pId;
  }

}
